libdwfl tries to model the loading of modules (executables, shared
libraries, the linux kernel and/or kernel modules) in
memory. Depending on the (offline) load address it then also applies
(simple) relocations for ET_REL (object files or kernel modules). Load
addresses are normally represented through phdr segments for ET_EXEC
or ET_DYN, but for ET_REL files (which don't have phdrs) the Elf
section sh_addr fields are used.
The sh_addr fields of the ET_REL Elf images are updated in two places
__libdwfl_elf_address_range (through __libdwfl_report_elf) and
__libdwfl_relocate (through dwfl_module_getelf and
dwfl_module_getdwarf). Both rely on sh_addr being zero if no load
address has been set yet, so the address layout for each (SHF_ALLOC)
section is done only once.
Recent linux kernels use a linker script that does set the sh_addr
fields to (random, linker assigned) non-zero addresses. See commit
1ba9f8979426 ("vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and related
macros").
The sh_addr values seems unnecessary, but because they aren't zero
anymore our layout/relocation code doesn't know it still has to figure
out a "real" load value for these sections.
Introduce __libdwfl_reset_sh_addr which resets all sh_addr fields to
zero for SHF_ALLOC sections in ET_REL files.
We don't call __libdwfl_reset_sh_addr on aux_sym files (from
.gnu_debugdata) and when constructing an Elf from a core file. In all
other cases we know (or assume) that the Elf file is being opened
through libdw_open_elf (called indirectly through __libdw_open_elf,
__libdwfl_report_offline, dwfl_module_getelf and dwfl_module_getdwarf)
This technically changes the Elf that goes through libdwfl, but we
would already update the sh_addr fields for ET_REL Elf sections in
memory anyway to represent the load address as libdwfl would see
them. So this isn't really a change in behavior (it just might update
the sh_addr field twice).
* libdwfl/libdwflP.h (__libdwfl_reset_sh_addr): Define new
internal function.
* libdwfl/relocate.c (__libdwfl_reset_sh_addr): New internal
function.
* libdwfl/open.c (libdw_open_elf): Call __libdwfl_reset_sh_addr.
* libdwfl/dwfl_module_getdwarf.c (open_elf_file): Add comment.
(find_aux_sym): Likewise.
(find_dw): Likewise.
* libdwfl/dwfl_segment_report_module.c
(dwfl_segment_report_module): Likewise.
* libdwfl/dwfl_report_elf.c (__libdwfl_report_elf): Likewise.
Signed-off-by: Mark Wielaard <mark@klomp.org>
Gbp-Pq: Name elfutils-0.194-sh_addr-non-zero.patch
if (*fd < 0)
return CBFAIL;
+ /* This will call __libdwfl_reset_sh_addr. */
return __libdw_open_file (fd, elf, true, false);
}
else if (unlikely (elf_kind (*elf) != ELF_K_ELF))
free (buffer);
else
{
+ /* We don't call __libdwfl_reset_sh_addr here, should we? */
mod->aux_sym.elf = elf_memory (buffer, size);
if (mod->aux_sym.elf == NULL)
free (buffer);
switch (mod->dwerr)
{
case DWFL_E_NOERROR:
+ /* main.elf already should have had __libdwfl_reset_sh_addr called. */
mod->debug.elf = mod->main.elf;
mod->debug.address_sync = mod->main.address_sync;
/* Preinstall the open ELF handle for the module. */
if (m->main.elf == NULL)
{
+ /* We assume all calls to __libdwfl_report_elf got their Elf
+ through __libdw_open_file which already called
+ __libdwfl_reset_sh_addr. */
m->main.elf = elf;
m->main.vaddr = vaddr;
m->main.address_sync = address_sync;
elf->flags |= ELF_F_MALLOCED;
}
+ /* Elf comes through a core file, so cannot be an ET_REL. Don't call
+ __libdwfl_reset_sh_addr. */
if (elf != NULL && mod->main.elf == NULL)
{
/* Install the file in the module. */
/* Find the main ELF file, update MOD->elferr and/or MOD->main.elf. */
extern void __libdwfl_getelf (Dwfl_Module *mod) internal_function;
+/* Reset all sh_addr fields to zero for SHF_ALLOC sections if the Elf
+ handle is an ET_REL. Called from libdw_open_elf. */
+extern void __libdwfl_reset_sh_addr (Elf *elf) internal_function;
+
/* Process relocations in debugging sections in an ET_REL file.
FILE must be opened with ELF_C_READ_MMAP_PRIVATE or ELF_C_READ,
to make it possible to relocate the data in place (or ELF_C_RDWR or
*fdp = -1;
}
+ __libdwfl_reset_sh_addr (elf);
+
*elfp = elf;
return error;
}
/* Relocate debug information.
Copyright (C) 2005-2011, 2014, 2016, 2018 Red Hat, Inc.
+ Copyright (C) 2026 Mark J. Wielaard <mark@klomp.org>
This file is part of elfutils.
This file is free software; you can redistribute it and/or modify
typedef uint8_t GElf_Byte;
+/* Reset all sh_addr fields to zero for SHF_ALLOC sections if the Elf
+ handle is an ET_REL. Called from libdw_open_elf. */
+
+void
+internal_function
+__libdwfl_reset_sh_addr (Elf *elf)
+{
+ if (elf == NULL || elf->kind != ELF_K_ELF)
+ return;
+
+ GElf_Ehdr ehdr_mem;
+ GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem);
+ if (ehdr == NULL || ehdr->e_type != ET_REL)
+ return;
+
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (elf, scn)) != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ if (shdr != NULL)
+ {
+ if ((shdr->sh_flags & SHF_ALLOC) != 0 && shdr->sh_addr != 0)
+ {
+ shdr_mem.sh_addr = 0;
+ gelf_update_shdr (scn, &shdr_mem);
+ }
+ }
+ }
+}
+
/* Adjust *VALUE to add the load address of the SHNDX section.
We update the section header in place to cache the result. */